﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using DotNetNuke.Services.FileSystem;

namespace BrandonHaynes.Providers
    {
    /// <summary>
    /// Allows "child portals" (strictly defined, see Extensions.GetParentPortal) 
    /// to create a symbolic link to the file system of their parent portal.  
    /// Only anonymous-readable files are linked, and these are exposed in a read-
    /// only manner.
    /// 
    /// Designed as a demonstration of a real-world folder provider, this may
    /// be useful as an initial prototype for further development.
    /// </summary>
    public class SymbolicLinkFolderProvider : FolderProvider
        {
        public override bool FileExists(IFolderInfo folder, string fileName)
            { return folder.ParentPortalOperation(provider => parentFolder => 
                provider.FileExists(parentFolder, fileName), false); }

        public override bool FolderExists(string folderPath, FolderMappingInfo folderMapping)
            { return folderMapping.ParentPortalOperation(
                         folderPath,
                         provider => folder => provider.FolderExists(folder.FolderPath, folder.GetMapping()),
                         Extensions.IsRootFolderOfSymbolicLink(folderPath));
            }

        public override FileAttributes? GetFileAttributes(IFileInfo file)
            { return file.ParentPortalOperation<FileAttributes?>(provider => provider.GetFileAttributes); }

        public override string[] GetFiles(IFolderInfo folder)
            { return folder.ParentPortalOperation(provider => provider.GetFiles, new string[0]); }

        public override long GetFileSize(IFileInfo file)
            { return file.ParentPortalOperation(provider => provider.GetFileSize, 0L); }

        public override Stream GetFileStream(IFileInfo file)
            { return file.ParentPortalOperation<Stream>(provider => provider.GetFileStream); }

        public override Stream GetFileStream(IFolderInfo folder, string fileName)
            { return folder.ParentPortalOperation<Stream>(provider => parentFolder => 
                provider.GetFileStream(parentFolder, fileName)); }

        public override string GetFileUrl(IFileInfo file)
            { return file.ParentPortalOperation<string>(provider => provider.GetFileUrl); }

        public override string GetFolderProviderIconPath()
            { return "/icons/sigma/Webserver_16x16_Standard.png"; }

        public override DateTime GetLastModificationTime(IFileInfo file)
            { return file.ParentPortalOperation(provider => 
                provider.GetLastModificationTime, DateTime.MinValue); }

        public override bool IsInSync(IFileInfo file)
            { return file.ParentPortalOperation(provider => provider.IsInSync, false); }

        public override IEnumerable<string> GetSubFolders(string folderPath, FolderMappingInfo folderMapping)
            {
            return folderMapping.ParentPortalOperation(
                folderPath,
                // Given a path, we want to identify those subfolders that are anonymous-readable
                provider => parentFolder => provider.GetSubFolders(parentFolder.FolderPath, parentFolder.GetMapping())
                    .Select(path => FolderManager.Instance.GetFolder(parentFolder.PortalID, path))
                    .Where(folder => folder.IsAnonymouslyReadable())
                    .Select(folder => folderPath + folder.FolderPath),
                Enumerable.Empty<string>());
            }

        public override void AddFolder(string folderPath, FolderMappingInfo folderMapping)
            {
            var parentPath = Regex.Replace(folderPath, "[^/]+/$", string.Empty);
            var parentPortal = folderMapping.GetParentPortal();

            // Creating a symbolic link to a parent parent ON a parent portal 
            // doesn't make sense.
            if(parentPortal == null)
                throw new NotSupportedException("Cross-portal folders may only exist in child portals.");
            // Creating subfolders inside the symbolic link would be cool, but is a 
            // bit of a nightmare (with respect to security and synchronization).
            // As such, we disallow nesting.
            else if (FolderManager.Instance.GetFolder(folderMapping.PortalID, parentPath).GetMapping().FolderProviderType == typeof(CrossPortalFolderProvider).FullName)
                throw new NotSupportedException("Cross-portal folders may not be nested.");

            // This should probably happen automatically by the core, but
            // here we do it manually.
            FolderManager.Instance.Synchronize(folderMapping.PortalID, folderPath, true, true);
            }

        public override void RenameFolder(IFolderInfo folder, string newFolderName)
            {
            // Disallow renaming anything except the root; otherwise we risk
            // making changes to the parent file system.
            if (!Extensions.IsRootFolderOfSymbolicLink(folder.FolderPath))
                throw new NotSupportedException("Only the root of a cross-portal folder may be renamed.");
            }

        // We might expect these functions to throw (deleting folders on a parent 
        // portal?) but remember that we're only deleting the FolderInfo/FileInfo
        // stubs that link the two entities.  As such, it's safe to do.
        // Additionally, admins will need to deleted these guys if we want to 
        // allow REMOVING the symbolic link.  A consequence of this is the fact
        // that an admin must remove all subfile/subfolders before being able
        // to remove the symbolic link.  This is a weakness in DNN, not here
        // (admins should be able to recursively delete).
        public override void DeleteFolder(IFolderInfo folder) { }
        public override void DeleteFile(IFileInfo file) { }

        // No file/folder context here, so just do whatever.  This is a bit weak.
        public override bool RequiresNetworkConnectivity { get { return true; } }
        public override bool SupportsFileAttributes() { return true; }

        #region Unsupported Operations

        public override void AddFile(IFolderInfo folder, string fileName, Stream content)
            { throw new NotSupportedException(); }

        public override void RenameFile(IFileInfo file, string newFileName)
            { throw new NotSupportedException(); }

        public override void SetFileAttributes(IFileInfo file, FileAttributes fileAttributes)
            { throw new NotSupportedException(); }

        public override void UpdateFile(IFileInfo file, Stream content)
            { throw new NotSupportedException(); }

        public override void UpdateFile(IFolderInfo folder, string fileName, Stream content)
            { throw new NotSupportedException(); }

        #endregion
        }
    }